package com.small.sentinel.slots.flow.controller;

import com.small.sentinel.node.Node;
import com.small.sentinel.util.TimeUtil;

import java.util.concurrent.atomic.AtomicLong;

/**
 * |
 * | coldInterval 0.3(3.3333333..)                               |
 * |                                          |  |
 * |                                       |     |
 * |                            slope   |        |
 * |                                 |           |
 * |                             |               |
 * |                          |                  |
 * |stableInterval 0.1(10)          |                    |
 * |-----------------------|                     |
 * |                       | warmUpPeriodInSec   |
 * |       ①              |      ②              |
 * |                      |                      |
 * |_______________________________________________________________________________________
 *                thresholdPermits(warningToken) maxPermits(maxToken)
 *                        25                       50
 *
 *
 *
 */
public class WarmUpController implements TrafficShapingController {
    //阀值 10  5s
    protected double count;
    //冷却因子 默认为3
    private int coldFactor;
    //警戒令牌数量，区分系统冷热状态 （预热时长*单机阀值）/（冷区因子-1）
    protected int warningToken = 0;
    //最大令牌数  警戒令牌+（2*预热时长*单机阀值）/（冷区因子+1）
    private int maxToken;
    //斜率    （冷却因子-1）/（阀值 * （最大令牌数 - 警戒令牌数量））
    protected double slope;

    protected AtomicLong storedTokens = new AtomicLong(0);
    protected AtomicLong lastFilledTime = new AtomicLong(0);

    public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
        construct(count, warmUpPeriodInSec, coldFactor);
    }

    public WarmUpController(double count, int warmUpPeriodInSec) {
        construct(count, warmUpPeriodInSec, 3);
    }

    private void construct(double count, int warmUpPeriodInSec, int coldFactor) {

        if (coldFactor <= 1) {
            throw new IllegalArgumentException("Cold factor should be larger than 1");
        }

        this.count = count;

        //冷却因子 默认为3
        //1.stableInterval = 1/count
        //2.coldInterval = stableInterval * coldFactor
        //②的面积：①的面积  =  （coldInterval-stableInterval）：stableInterval   梯形区域是红色1的长方形区域的两倍
        // thresholdPermits = 0.5 * warmupPeriod / stableInterval.
        // warningToken = 100;
        warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
        //  maxPermits = thresholdPermits + 2 * warmupPeriod /(stableInterval + coldInterval)
        // maxToken = 200
        maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
        // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
        slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
    }

    @Override
    public boolean canPass(Node node, int acquireCount) {

        //获取当前窗口通过qps
        long passQps = (long)node.passQps();
        //获取上个窗口通过的qps
        long previousQps = (long)node.previousPassQps();
        //同步令牌
        syncToken(previousQps);
        long restToken = storedTokens.get();
        //处于冷启动状态
        if (restToken >= warningToken){
            long aboveToken = restToken - warningToken;
            // 消耗的速度要比warning快，但是要比慢
            // y轴，当前token生成时间 current interval = restToken*slope+stableInterval
            // 计算此时1s内能够生成token的数量(根据斜率计算)  (y2-y1)/(x2-x1) = slope
            double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
            if (passQps+acquireCount <= warningQps){
                return true;
            }
        }else {
            if (passQps+acquireCount <= count){
                return true;
            }
        }
        return false;
    }

    /**
     * 同步令牌
     * @param passQps
     */
    private void syncToken(long passQps) {
        //1.判断当前时间是否小于上次发放令牌时间
        long currentTime = TimeUtil.currentTimeMillis();
        currentTime = currentTime - currentTime % 1000;
        //上一次同步时间
        long oldLastFillTime = lastFilledTime.get();
        //说明还是上一个窗口 直接返回
        //令牌桶一秒更新一次
        if (currentTime <= oldLastFillTime) {
            return;
        }

        //2.获取剩余令牌桶数量
        long oldValue = storedTokens.get();
        //3.计算令牌桶现在应该有的数量
        long newValue = coolDownTokens(currentTime, passQps);
        //4.更新令牌桶
        if (storedTokens.compareAndSet(oldValue, newValue)) {
            // 减去上一个时间窗口的通过请求数
            long currentValue = storedTokens.addAndGet(0 - passQps);
            if (currentValue < 0) {
                storedTokens.set(0L);
            }
            lastFilledTime.set(currentTime);
        }
    }

    /**
     * 冷却令牌
     * @param currentTime
     * @param passQps
     * @return
     */
    private long coolDownTokens(long currentTime, long passQps) {

        //获取剩余令牌
        long oldValue = storedTokens.get();
        long newValue = oldValue;
        // 添加令牌的判断前提条件:
        //处于业务高发期 当令牌的消耗程度远远低于警戒线的时候
        if (oldValue < warningToken){
            newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
        //处于冷启动状态
        }else if (oldValue > warningToken){
            //消耗速度低于冷却速度，则补充令牌
            if (passQps < (int)count / coldFactor) {
                newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
            }
        }
        // 当令牌桶满了之后，抛弃多余的令牌
        return Math.min(newValue, maxToken);
    }
}
